#ifndef a8_thread_h
#define a8_thread_h

#include <assert.h>
#include <a8/atomic.hpp>
#include <a8/delegate.hpp>

#if defined(_WIN32)
#include<windows.h>
#else
#include<pthread.h>
#endif

namespace a8
{
  static void*
#if defined(_WIN32)
  __stdcall
#endif
  ThreadWrapper(void* threadParam);

  class Thread
  {
  public:
    Thread():mId(0), mShutdown(-1)
    {
    }

    virtual ~Thread()
    {
      Stop();
      Join();
    }

    void Start()
    {
      assert(mId == 0);
#if defined(_WIN32)
      mThread = (HANDLE)_beginthreadex(NULL, 0, ThreadWrapper, this, 0, &mId);
      assert(mThread);
#else
      if(pthread_create(&mId, 0, ThreadWrapper, this) != 0)
	assert(0);
#endif
      mShutdown = 0;
    }

    virtual void Stop()
    {
      InterlockedIncrement(&mShutdown);
    }

    void Join()
    {
      if(mId == 0)
	return;

#if defined(_WIN32)
      DWORD exitCode;
      while(1)
      {
	if(GetExitCodeThread(mThread, &exitCode) != 0)
	{
	  if(exitCode != STILL_ACTIVE)
	    break;
	  else
	    WaitForSingleObject(mThread, INFINITE);
	}else
	  break;
      }
      CloseHandle(mThread);
#else
      void * stat;
      if(mId != pthread_self())
      {
	int r = pthread_join(mId, &stat);
	if(r != 0)
	  assert(0);
      }
#endif
      mId = 0;
    }

    bool IsShutdown()
    {
      return mShutdown == 1;
    }

    virtual void ThreadFunc() = 0;
  protected:
    volatile unsigned long mShutdown;
#if defined(_WIN32)
    HANDLE mThread;
    unsigned int mId;        
#else
    pthread_t mId;
#endif
  };

  static void*
#if defined(_WIN32)
  __stdcall
#endif
  ThreadWrapper(void* threadParam)
  {
#ifdef USE_PROFILER
    ProfilerRegisterThread();
#endif
    assert(threadParam);
    Thread* t = static_cast<Thread*>(threadParam);

    assert(t);
    t->ThreadFunc();
#if defined(_WIN32)
    _endthreadex(0);
#endif
    return 0;
  }

  class Runnable: public Thread
  {
  public:
    void* UserData;
    a8::Delegate<void (Runnable*)> OnExecute;

    Runnable():Thread(), UserData(NULL) {}
    virtual void ThreadFunc()
    {
      OnExecute(this);
    }
  };
}

#endif
